package com.zxt.content.copywriting.aop;

import com.alibaba.fastjson.JSON;
import com.zxt.content.common.Bean.Result;
import com.zxt.content.common.exception.ServiceException;
import com.zxt.content.copywriting.aop.req.BaseDTO;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.MessageFormat;
import java.util.Objects;

/**
 * 接口出入参日志打印-aop实现
 * 需要注意的点
 * 1.@Around("writeLogPointcut() && args(request)")  这里需要符合参数条件，类型是BaseDTO
 * 2.被代理的方法必须是public
 *
 * @author zxt
 */
@Slf4j
@Component
@Aspect
public class LogAspect {

    /**
     * @Lazy 产生循环依赖，使用懒加载避开循环依赖
     */
    @Autowired
    @Lazy
    private LogAspect self;

    @Pointcut("execution(* com.zxt.content.copywriting.controller.PublicCopywritingController.*(..)) " +
            "&& !@annotation(com.zxt.content.copywriting.aop.LogExcludeAnnotation)")
    public void writeLogPointcut() {
    }

    @Around("writeLogPointcut() && args(request)")
    public <T extends Result> Result<T> handle(ProceedingJoinPoint joinPoint, BaseDTO request) {
        long requestTime = System.currentTimeMillis();
        Result<T> result = null;
        Throwable throwable = null;
        try {
            Object proceed = joinPoint.proceed();
            result = (T) proceed;
        } catch (ServiceException exception) {
            result = new Result<>();
            result.setCode(exception.getCode());
            result.setMessage(exception.getMessage());
        } catch (Throwable exception) {
            result = new Result<>();
            result.setCode(500);
            throwable = exception;
        } finally {
            self.writeLog(request, result, requestTime, throwable, joinPoint);
        }

        return result;
    }

    /**
     * 异步日志-目前info日志 为异步
     *
     * @param request
     * @param result
     * @param requestTime
     * @param throwable
     * @param joinPoint
     */
    @Async
    public void writeLog(BaseDTO request, Result result, long requestTime, Throwable throwable,
                         ProceedingJoinPoint joinPoint) {
        if (Objects.nonNull(throwable)) {
            throwable.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        Signature signature = joinPoint.getSignature();
        String methodName = signature.getName();
        String controllerName = signature.getDeclaringTypeName();
        String methodFullName = MessageFormat.format("{0}.{1}", controllerName, methodName);
        long elapsed = endTime - requestTime;
        String format = "method:{0},request:{1},response:{2},code:{3,number,#}," +
                "msg:{4},requestTime:{5,number,#},elapsed:{6}";
        if (throwable == null) {
            log.info(MessageFormat.format(format,
                    methodFullName,
                    JSON.toJSONString(request),
                    JSON.toJSONString(result),
                    result.getCode(),
                    result.getMessage(),
                    requestTime,
                    elapsed));
        } else {
            String message = throwable == null ? result.getMessage() : getStackTrace(throwable);
            log.error(MessageFormat.format(format,
                    methodFullName,
                    JSON.toJSONString(request),
                    JSON.toJSONString(result),
                    result.getCode(),
                    message,
                    requestTime,
                    elapsed));
        }
    }

    private String getStackTrace(Throwable throwable) {
        StringWriter stringWriter = new StringWriter();
        try (PrintWriter printWriter = new PrintWriter(stringWriter)) {
            throwable.printStackTrace(printWriter);
            return stringWriter.toString();
        }
    }

}
